package com.yixing.tech.datasync.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.convert.ConvertUtils;
import com.yixing.tech.common.service.AbstractWmsService;
import com.yixing.tech.datasync.entity.ProductionInStock;
import com.yixing.tech.datasync.entity.RawInStock;
import com.yixing.tech.datasync.entity.SalesReturnStock;
import com.yixing.tech.datasync.mapper.ProductionInStockMapper;
import com.yixing.tech.datasync.mapper.RawInStockMapper;
import com.yixing.tech.datasync.mapper.SalesReturnStockMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.yixing.tech.common.CommonConstant.*;

/**
 * @author yixing tech
 * @since 1.0.0
 */
@Slf4j
@Service
public class InStockSyncService extends AbstractWmsService {

    @Autowired
    private RawInStockMapper rawInStockMapper;
    @Autowired
    private ProductionInStockMapper productionInStockMapper;
    @Autowired
    private SalesReturnStockMapper salesReturnStockMapper;
    @Value("${tenant-id: 6204cb763da841718ddd1cf8}")
    private String tenantId;

    @Transactional(rollbackFor = Exception.class)
    public boolean syncSalesReturnStock() throws BusinessException {
        // 查询
        List<SalesReturnStock> salesReturnStockList = salesReturnStockMapper.selectList(new QueryWrapper<SalesReturnStock>().eq("status", 0));
        log.info("同步销售退库数量: {}", salesReturnStockList.size());
        if (CollectionUtils.isEmpty(salesReturnStockList)) {
            return true;
        }
        // 找到物料编码
        Set<String> materialCodes = salesReturnStockList.stream().map(SalesReturnStock::getMaterialCode).collect(Collectors.toSet());
        List<Document> materials = findMaterials(materialCodes);
        Map<String, String> materialAbcMap = new HashMap<>();
        Map<String, Document> materialMap = new HashMap<>();
        materials.forEach(m -> {
            String abc = ConvertUtils.convertIfPossible(m.get("abc"), String.class);
            if (abc == null) {
                String levelId = m.getEmbedded(Arrays.asList("level", "id"), String.class);
                if (levelId != null) {
                    if (MaterialLevel.A.getCode().equals(levelId)) {
                        abc = A;
                    } else if (MaterialLevel.B.getCode().equals(levelId)) {
                        abc = B;
                    }
                }
            }
            materialAbcMap.put(m.get("code").toString(), abc);
            materialMap.put(m.get("code").toString(), m);
        });
        // 按ABC分类分组
        // 按照 采购收货单号：ABC分类 进行分组
        Map<String, List<SalesReturnStock>> rawInStockMapList = new HashMap<>();
        for (SalesReturnStock x : salesReturnStockList) {
            String materialCode = x.getMaterialCode();
            String abc = materialAbcMap.get(materialCode);
            if (StringUtils.isBlank(abc)) {
                throw new BusinessException(String.format("物料[%s]未设置物料ABC分类信息，同步失败", materialCode));
            }
            String key = x.getBillNumber();
            rawInStockMapList.computeIfAbsent(key, l -> new ArrayList<>()).add(x);
        }

        ObjectMapper objectMapper = new ObjectMapper();

        List<Document> inOrders = new LinkedList<>();
        List<Document> dtls = new LinkedList<>();

        for (String k : rawInStockMapList.keySet()) {
            List<SalesReturnStock> v = rawInStockMapList.get(k);
            String billNumber = k;
            String inOrderCode = new ObjectId().toString();
            Document inOrder = new Document();
            inOrder.put("inOrder", inOrderCode);
            inOrder.put("inSource", billNumber);
            inOrder.put("dept", "");
            inOrder.put("sourceType", newDocument("id", new ObjectId(SourceType.sync.getCode())));

            SalesReturnStock first = v.get(0);
            inOrder.put("erpBizType", first.getBizType());
            inOrder.put("erpTransactionType", first.getTransactionType());
            inOrder.put("erpCostCenterOrgUnit", first.getCostCenterOrgUnit());
            inOrder.put("erpStorageOrgUnit", first.getStorageOrgUnit());
            inOrder.put("erpAdminOrgUnit", first.getAdminOrgUnit());

            inOrder.put("createTime", new Date());
            inOrder.put("tenantId", tenantId);
            inOrder.put("isDelete", 0);
            inOrders.add(inOrder);

            for (SalesReturnStock x : v) {
                String materialCode = x.getMaterialCode();
                Document material = materialMap.get(materialCode);
                if (material == null) {
                    throw new BusinessException(String.format("物料[%s]不存在，同步失败", materialCode));
                }
                Document check = simpleFindOne("entity_" + RAW_MATERIAL_IN_STOCK_DETAIL, "source", "QMS", "inDetailSource", x.getSourceBillEntryId());
                if (check != null) {
                    throw new BusinessException(String.format("QMS入库单[%s]已存在，同步失败", x.getSourceBillEntryId()));
                }
                Document dtl = new Document();
                dtl.put("materialCode", x.getMaterialCode());
                dtl.put("materialName", material.get("materialName"));
                dtl.put("colorName", x.getColorName());
                dtl.put("languageName", x.getLanguageName());
                dtl.put("erpWareHouse", x.getWarehouse());

                Double planInNum = ConvertUtils.convertIfPossible(x.getStorageQuantity(), Double.class, null);
                if (planInNum == null) {
                    throw new BusinessException(String.format("单据[%s]入库数量不合法，同步失败", x.getSourceBillId()));
                }

                List<Document> units = material.getList("unit", Document.class);
                if (!CollectionUtils.isEmpty(units)) {
                    dtl.put("unit", units.get(0).getString("name"));
                }
                dtl.put("batchNo", x.getBatchNo());
                dtl.put("planInNum", planInNum);
                dtl.put("inSource", x.getSourceBillId());
                dtl.put("inDetailSource", x.getSourceBillEntryId());
                dtl.put("inOrder", inOrderCode);
                dtl.put("source", "QMS");
                dtl.put("originData", objectMapper.convertValue(x, Map.class));

                dtl.put("abc", materialAbcMap.get(materialCode));
                dtl.put("isApplyContainer", false);

                dtl.put("createTime", new Date());
                dtl.put("tenantId", tenantId);
                dtl.put("isDelete", 0);
                dtls.add(dtl);
            }
        }

        // 保存
        mongoTemplate.insert(inOrders, "entity_" + SALES_RETURN_STOCK);
        mongoTemplate.insert(dtls, "entity_" + SALES_RETURN_STOCK_DTL);

        // 更新状态
        salesReturnStockList.forEach(x -> x.setStatus(1));
        salesReturnStockMapper.updateById(salesReturnStockList);

        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean syncRawInStock() throws BusinessException {
        // 查询
        List<RawInStock> rawInStockList = rawInStockMapper.selectList(new QueryWrapper<RawInStock>().eq("status", 0));
        log.info("同步QMS原材料数量: {}", rawInStockList.size());
        if (CollectionUtils.isEmpty(rawInStockList)) {
            return true;
        }
        // 找到物料编码
        Set<String> materialCodes = rawInStockList.stream().map(RawInStock::getMaterialCode).collect(Collectors.toSet());
        List<Document> materials = findMaterials(materialCodes);
        Map<String, String> materialAbcMap = new HashMap<>();
        Map<String, Document> materialMap = new HashMap<>();
        materials.forEach(m -> {
            String abc = ConvertUtils.convertIfPossible(m.get("abc"), String.class);
            if (abc == null) {
                String levelId = m.getEmbedded(Arrays.asList("level", "id"), String.class);
                if (levelId != null) {
                    if (MaterialLevel.A.getCode().equals(levelId)) {
                        abc = A;
                    } else if (MaterialLevel.B.getCode().equals(levelId)) {
                        abc = B;
                    }
                }
            }
            materialAbcMap.put(m.get("code").toString(), abc);
            materialMap.put(m.get("code").toString(), m);
        });
        // 按ABC分类分组
        // 按照 采购收货单号：ABC分类 进行分组
        Map<String, List<RawInStock>> rawInStockMapList = new HashMap<>();
        for (RawInStock x : rawInStockList) {
            String materialCode = x.getMaterialCode();
            String abc = materialAbcMap.get(materialCode);
            if (StringUtils.isBlank(abc)) {
                throw new BusinessException(String.format("物料[%s]未设置物料ABC分类信息，同步失败", materialCode));
            }
            String key = x.getPurBillNumber();
            rawInStockMapList.computeIfAbsent(key, l -> new ArrayList<>()).add(x);
        }

        ObjectMapper objectMapper = new ObjectMapper();

        List<Document> inOrders = new LinkedList<>();
        List<Document> dtls = new LinkedList<>();

        for (String k : rawInStockMapList.keySet()) {
            List<RawInStock> v = rawInStockMapList.get(k);
            String purBillNumber = k;
            String inOrderCode = new ObjectId().toString();
            Document inOrder = new Document();
            inOrder.put("inOrder", inOrderCode);
            inOrder.put("inSource", purBillNumber);
            inOrder.put("dept", "");
            inOrder.put("sourceType", newDocument("id", new ObjectId(SourceType.sync.getCode())));

            RawInStock first = v.get(0);
            inOrder.put("erpBizType", first.getBizType());
            inOrder.put("erpTransactionType", first.getTransactionType());
            inOrder.put("erpCostCenterOrgUnit", first.getCostCenterOrgUnit());
            inOrder.put("erpStorageOrgUnit", first.getStorageOrgUnit());
            inOrder.put("erpAdminOrgUnit", first.getAdminOrgUnit());

            inOrder.put("createTime", new Date());
            inOrder.put("tenantId", tenantId);
            inOrder.put("isDelete", 0);
            inOrders.add(inOrder);

            for (RawInStock x : v) {
                String materialCode = x.getMaterialCode();
                Document material = materialMap.get(materialCode);
                if (material == null) {
                    throw new BusinessException(String.format("物料[%s]不存在，同步失败", materialCode));
                }
                Document check = simpleFindOne("entity_" + RAW_MATERIAL_IN_STOCK_DETAIL, "source", "QMS", "inDetailSource", x.getSourceBillEntryId());
                if (check != null) {
                    throw new BusinessException(String.format("QMS入库单[%s]已存在，同步失败", x.getSourceBillEntryId()));
                }
                Document dtl = new Document();
                dtl.put("materialCode", x.getMaterialCode());
                dtl.put("materialName", material.get("materialName"));
                dtl.put("colorName", x.getColorName());
                dtl.put("languageName", x.getLanguageName());
                dtl.put("erpWareHouse", x.getWarehouse());

                Double planInNum = ConvertUtils.convertIfPossible(x.getStorageQuantity(), Double.class, null);
                if (planInNum == null) {
                    throw new BusinessException(String.format("单据[%s]入库数量不合法，同步失败", x.getSourceBillId()));
                }

                List<Document> units = material.getList("unit", Document.class);
                if (!CollectionUtils.isEmpty(units)) {
                    dtl.put("unit", units.get(0).getString("name"));
                }
                dtl.put("batchNo", x.getBatchNo());
                dtl.put("planInNum", planInNum);
                dtl.put("inSource", x.getSourceBillId());
                dtl.put("inDetailSource", x.getSourceBillEntryId());
                dtl.put("inOrder", inOrderCode);
                dtl.put("source", "QMS");
                dtl.put("qmsRawInStock", objectMapper.convertValue(x, Map.class));

                dtl.put("abc", materialAbcMap.get(materialCode));
                dtl.put("isApplyContainer", false);

                dtl.put("createTime", new Date());
                dtl.put("tenantId", tenantId);
                dtl.put("isDelete", 0);
                dtls.add(dtl);
            }
        }

        // 保存
        mongoTemplate.insert(inOrders, "entity_" + RAW_MATERIAL_IN_STOCK);
        mongoTemplate.insert(dtls, "entity_" + RAW_MATERIAL_IN_STOCK_DETAIL);

        // 更新状态
        rawInStockList.forEach(x -> x.setStatus(1));
        rawInStockMapper.updateById(rawInStockList);

        return true;
    }


    @Transactional(rollbackFor = Exception.class)
    public boolean syncProductionInStock() throws BusinessException {
        // 查询
        List<ProductionInStock> list = productionInStockMapper.selectList(new QueryWrapper<ProductionInStock>().eq("status", 0));
        log.info("同步QMS成品入库数量: {}", list.size());
        if (CollectionUtils.isEmpty(list)) {
            return true;
        }
        ObjectMapper objectMapper = new ObjectMapper();
        // 找到物料编码
        Set<String> materialCodes = list.stream().map(ProductionInStock::getMaterialCode).collect(Collectors.toSet());
        List<Document> materials = findMaterials(materialCodes);
        Map<String, Document> materialMap = new HashMap<>();
        materials.forEach(m -> materialMap.put(m.get("code").toString(), m));
        // 按照源单ID进行分组
        Map<String, List<ProductionInStock>> mapList = new HashMap<>();
        for (ProductionInStock x : list) {
            String key = x.getSourceBillId();
            mapList.computeIfAbsent(key, l -> new ArrayList<>()).add(x);
        }

        List<Document> inOrders = new LinkedList<>();
        List<Document> dtls = new LinkedList<>();
        List<Document> stocks = new LinkedList<>();
        for (String sourceBillId : mapList.keySet()) {
            List<ProductionInStock> v = mapList.get(sourceBillId);
            String inOrderCode = new ObjectId().toString();
            String containerCode = v.get(0).getContainerCode();
            if (StringUtils.isBlank(containerCode)) {
                throw new BusinessException("容器不能为空，同步失败");
            }
            Document container = simpleFindOne("entity_" + CONTAINER, "code", containerCode);
            if (container == null) {
                throw new BusinessException(String.format("容器[%s]不存在，同步失败", containerCode));
            }

            Document inOrder = new Document();
            inOrder.put("inOrder", inOrderCode);
            inOrder.put("inSource", sourceBillId);
            inOrder.put("dept", "");
//            inOrder.put("sourceLocation", locationCode);
            inOrder.put("sourceContainer", containerCode);
            inOrder.put("sourceType", newDocument("id", new ObjectId(SourceType.sync.getCode())));
            inOrder.put("isStored", false);

            ProductionInStock first = v.get(0);
            inOrder.put("erpBizType", first.getBizType());
            inOrder.put("erpTransactionType", first.getTransactionType());
            inOrder.put("erpCostCenterOrgUnit", first.getCostCenterOrgUnit());
            inOrder.put("erpStorageOrgUnit", first.getStorageOrgUnit());
            inOrder.put("erpAdminOrgUnit", first.getAdminOrgUnit());

            inOrder.put("createTime", new Date());
            inOrder.put("tenantId", tenantId);
            inOrder.put("isDelete", 0);
            inOrders.add(inOrder);

            for (ProductionInStock x : v) {
                String materialCode = x.getMaterialCode();
                Document material = materialMap.get(materialCode);
                if (material == null) {
                    throw new BusinessException(String.format("物料[%s]不存在，同步失败", materialCode));
                }
                Document check = simpleFindOne("entity_" + PRODUCTION_IN_STOCK_DETAIL, "source", "QMS", "inSource", x.getSourceBillId());
                if (check != null) {
                    throw new BusinessException(String.format("QMS成品入库单[%s]已存在，同步失败", x.getSourceBillId()));
                }
//                if (location.getBoolean("lock")) {
//                    throw new BusinessException(String.format("库位[%s]处于锁定状态，同步失败", locationCode));
//                }
                Double planInNum = ConvertUtils.convertIfPossible(x.getStorageQuantity(), Double.class, null);
                if (planInNum == null) {
                    throw new BusinessException(String.format("单据[%s]入库数量不合法，同步失败", x.getSourceBillId()));
                }

                Document dtl = new Document();
                dtl.put("erpWareHouse", x.getWarehouseCode());
                dtl.put("materialCode", x.getMaterialCode());
                dtl.put("materialName", material.get("materialName"));
                dtl.put("colorName", x.getColorName());
                dtl.put("languageName", x.getLanguageName());
                dtl.put("erpCostObject", x.getCostObject());
                List<Document> units = material.getList("unit", Document.class);
                if (!CollectionUtils.isEmpty(units)) {
                    dtl.put("unit", units.get(0).getString("name"));
                }
                dtl.put("batchNo", x.getBatchNo());
                dtl.put("planInNum", planInNum);
                dtl.put("inSource", x.getSourceBillId());
                dtl.put("inOrder", inOrderCode);
                dtl.put("productionOrder", x.getOrderCode());
//                dtl.put("sourceLocation", x.getLocationCode());
                dtl.put("sourceContainer", x.getContainerCode());
                dtl.put("ruleCode", x.getRuleNumber());
                dtl.put("source", "QMS");
                String dtlKey = new ObjectId().toString();
                dtl.put("dtlKey", dtlKey);
                dtl.put("qmsProductionInStock", objectMapper.convertValue(x, Map.class));

                dtl.put("createTime", new Date());
                dtl.put("tenantId", tenantId);
                dtl.put("isDelete", 0);
                dtls.add(dtl);

                // 库存
                Document stock = new Document();
                stock.put("containerCode", x.getContainerCode());
                stock.put("materialCode", x.getMaterialCode());
                stock.put("partitionSide", "1");
                stock.put("batchNo", x.getBatchNo());
                stock.put("initNum", planInNum);
                stock.put("realNum", planInNum);
                stock.put("colorName", x.getColorName());
                stock.put("languageName", x.getLanguageName());
                stock.put("productionInStockDtlKey", dtlKey);
                stock.put("createTime", new Date());
                stock.put("tenantId", tenantId);
                stock.put("isDelete", 0);
                stocks.add(stock);
            }
        }

        // 保存
        mongoTemplate.insert(inOrders, "entity_" + PRODUCTION_IN_STOCK);
        mongoTemplate.insert(dtls, "entity_" + PRODUCTION_IN_STOCK_DETAIL);
        mongoTemplate.insert(stocks, "entity_" + STOCK_NAME);

        // 更新状态
        list.forEach(x -> x.setStatus(1));
        productionInStockMapper.updateById(list);
        return true;
    }

    private List<Document> findMaterials(Collection<String> materialCodes) {
        Criteria match = newCriteria().and("code").in(materialCodes);
        List<AggregationOperation> operations = new LinkedList<>();
        operations.add(Aggregation.match(match));
        operations.add(Aggregation.lookup("dict", "$unit.id", "_id", "unit"));
        return aggregateResult(operations, "entity_" + MATERIAL);

    }


}
